package cn.org.xwb.main.utils;

import lombok.extern.log4j.Log4j2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 和反射相关的工具类
 */
@Log4j2
public class ReflectUtil {

    private ReflectUtil() {
    }

    /**
     * 方法类型
     */
    public static class MethodType {

        private MethodType() {}

        /**
         * 方法类型为 get 方法
         */
        public static String METHOD_TYPE_GET = "get";

        /**
         * 方法类型为 set 方法
         */
        public static String METHOD_TYPE_SET = "set";
    }

    /**
     * 对象字段处理器
     */
    @FunctionalInterface
    public interface FieldHandler {

        /**
         * 执行对象字段处理
         *
         * @param object 对象
         * @param field 对象字段
         * @param annotations 当前处理字段的所有注解
         */
        void doFieldHandle(Object object, Field field, Annotation[] annotations);

    }

    /**
     * 判断方法、参数...被标注的所有注解中是否包含某个注解
     *
     * @param annotations 被标注的所有注解
     * @param annotation  需要进行判断是否包含的注解
     * @return true：包含；false：不包含
     */
    public static boolean judgeContainAnnotation(Annotation[] annotations, Annotation annotation) {
        return Arrays.stream(annotations).anyMatch(
                annotationPart -> annotationPart.annotationType().equals(annotation.annotationType())
        );
    }

    /**
     * 判断方法、参数...被标注的所有注解中是否包含某个注解
     *
     * @param annotations 被标注的所有注解
     * @param annotationClass  需要进行判断是否包含的注解
     * @return true：包含；false：不包含
     */
    public static boolean judgeContainAnnotation(Annotation[] annotations, Class<? extends Annotation> annotationClass) {
        return Arrays.stream(annotations).anyMatch(
                annotationPart -> annotationPart.annotationType().equals(annotationClass)
        );
    }

    /**
     * 获取指定字段在指定类中的 getter 方法
     *
     * @param field 字段
     * @param clazz 类
     * @return 指定字段在指定类中的 getter 方法
     */
    public static Method getFieldGetMethod(Field field, Class<?> clazz) {
        // 获取字段对应的 getter 方法的方法名
        String fieldSetMethodName = getFieldGetSetMethodName(MethodType.METHOD_TYPE_GET, field);
        try {
            // 获取字段的类型
            Class<?> fieldType = field.getType();
            // 根据方法名和方法参数类型获取指定字段在指定类中的 getter 方法
            Method fieldGetMethod = clazz.getMethod(fieldSetMethodName, fieldType);
            // 保证方法可以被访问
            fieldGetMethod.setAccessible(true);
            log.info("获取字段 {} 在指定类 {} 中的 getter 方法 {} 成功", field, clazz, fieldSetMethodName);
            return fieldGetMethod;
        } catch (NoSuchMethodException e) {
            log.error("获取字段 {} 在指定类 {} 中的 getter 方法 {} 失败，\n错误信息：{}，\nerror：{}", field, clazz, fieldSetMethodName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取指定字段在指定类中的 setter 方法
     *
     * @param field 字段
     * @param clazz 类
     * @return 指定字段在指定类中的 setter 方法
     */
    public static Method getFieldSetMethod(Field field, Class<?> clazz) {
        // 获取字段对应的 setter 方法的方法名
        String fieldSetMethodName = getFieldGetSetMethodName(MethodType.METHOD_TYPE_SET, field);
        try {
            // 获取字段的类型
            Class<?> fieldType = field.getType();
            // 根据方法名和方法参数类型获取指定字段在指定类中的 setter 方法
            Method fieldSetMethod = clazz.getMethod(fieldSetMethodName, fieldType);
            // 保证方法可以被访问
            fieldSetMethod.setAccessible(true);
            log.info("获取字段 {} 在指定类 {} 中的 setter 方法 {} 成功", field, clazz, fieldSetMethodName);
            return fieldSetMethod;
        } catch (NoSuchMethodException e) {
            log.error("获取字段 {} 在指定类 {} 中的 setter 方法 {} 失败，\n错误信息：{}，\nerror：{}", field, clazz, fieldSetMethodName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取指定字段的 getter / setter 方法的方法名
     *
     * @param methodType 方法类型
     * @param field 要获取 getter / setter 方法的字段
     * @return 指定字段的 getter / setter 方法的方法名
     */
    public static String getFieldGetSetMethodName(String methodType, Field field) {
        // 保证字段能够被访问
        field.setAccessible(true);
        // 获取字段名
        String fieldName = field.getName();
        // 拼接获取字段对应的方法类型的方法名
        return methodType + fieldName.substring(0,  1).toUpperCase() + fieldName.substring(1);
    }

    /**
     * 获取指定对象的指定字段的值
     *
     * @param object 对象
     * @param field 字段
     * @return 指定字段的值
     */
    public static Object getFieldValue(Object object, Field field) {
        try {
            // 保证字段可以访问
            field.setAccessible(true);
            // 获取并返回指定对象的指定字段的值
            log.info("获取对象 {} 中的字段 {} 的值成功", object, field);
            return field.get(object);
        } catch (IllegalAccessException e) {
            log.error("获取对象 {} 中的字段 {} 的值失败，\n错误信息：{}，\nerror：{}", object, field, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取指定对象中的指定字段(不包含父类中的字段)。
     * 此方法在获取指定对象中的指定字段时，会保证获取的指定字段能够被访问。
     *
     * @param object 要获取字段的指定对象
     * @param fieldName 要获取的指定字段的名称
     * @return 指定对象中的指定字段
     */
    public static Field getField(Object object, String fieldName) {
        try {
            // 获取指定对象的 Class
            Class<?> objectClass = object.getClass();
            // 获取指定对象中的指定字段
            Field declaredField = objectClass.getDeclaredField(fieldName);
            // 保证获取的指定字段能够被访问
            declaredField.setAccessible(true);
            log.info("获取对象 {} 中的字段 {} 成功", object, fieldName);
            return declaredField;
        } catch (NoSuchFieldException e) {
            log.error("获取对象 {} 中的字段 {} 失败，\n错误信息：{}，\nerror：{}", object, fieldName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 通过反射为字符串对象的 value 字段重新赋值为 strValue，
     * 从而保证不修改字符串对象的引用，并且能够修改字符串的取值
     * 由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了，所以要能够使用运行此方法，
     * 需要在运行项目时，添加虚拟机(VM)选项：--add-opens java.base/java.lang=ALL-UNNAMED
     * 开启默认不被允许的行为。
     *
     * @param str 需要进行重新赋值的字符串对象
     * @param strValue 要赋值给字符串对象的值
     */
    public static void setValueString(String str, String strValue) {
        try {
            // 获取字符串的 value 字段
            Field strValueField = getField(str, "value");
            // 为字符串对象的 value 字段重新赋值
            // strValueField.set(str, strValue.getBytes(StandardCharsets.UTF_8)); 不要使用该种方法，不会出现乱码
            // 采用如下方式，获取 strValue 的 value 字段值，将其赋值给 str 的 value 字段
            strValueField.set(str, strValueField.get(strValue));
            log.info("为字符串 {} 重新赋值为 {} 成功", str, strValue);
        } catch (Exception e) {
            log.error("为字符串 {} 重新赋值为 {} 失败，\n错误信息：{}，\nerror：{}", str, strValue, e.getMessage(), e);
        }
    }

    /**
     * 对对象中的所有字段进行处理
     *
     * @param object 对象
     * @param fieldHandler 对象字段处理器
     */
    public static void doObjAllFieldHandle(Object object, FieldHandler fieldHandler) {
        try {
            // 获取指定对象的 Class
            Class<?> objectClass = object.getClass();
            // 获取指定对象 Class 中的所有字段(不包括父类的)
            Field[] fields = objectClass.getDeclaredFields();
            // 遍历每个字段，对每个字段进行处理
            for (Field field : fields) {
                // 保证当前字段能够访问
                field.setAccessible(true);
                // 对当前遍历字段进行处理
                fieldHandler.doFieldHandle(object, field, field.getAnnotations());
            }
            log.info("对对象 {} 中的所有字段进行处理成功", object);
        } catch (Exception e) {
            log.error("对对象 {} 中的所有字段进行处理失败，\n错误信息：{}，\nerror：{}", object, e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 对对象中的所有字段根据注解进行处理
     *
     * @param object 对象
     * @param fieldHandler 对象字段处理器
     */
    public static void doObjAllFieldHandleByAnnotation(Object object, FieldHandler fieldHandler) {
        try {
            doObjAllFieldHandle(
                    object,
                    (Object obj, Field field, Annotation[] annotations) -> {
                        // 获取当前字段的所有注解
                        Annotation[] fieldAnnotations = field.getAnnotations();
                        // 对当前字段根据注解进行处理
                        fieldHandler.doFieldHandle(obj, field, fieldAnnotations);
                    }
            );
            log.info("对对象 {} 中的所有字段根据注解进行处理成功", object);
        } catch (Exception e) {
            log.error("对对象 {} 中的所有字段根据注解进行处理失败，\n错误信息：{}，\nerror：{}", object, e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取方法的方法名
     *
     * @param method 方法
     * @return 方法的方法名
     */
    public static String getMethodName(Method method) {
        return method.getName();
    }

}
